#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import re
import sys
import mrget
import shutil
import jinja2
import itertools
import subprocess
import lxml.html
import urllib.request
import mirrors.plugin


class Main:

    def __init__(self):
        self.url = "https://www.kernel.org"

        self.id = mirrors.plugin.params["id"]
        self.cfg = mirrors.plugin.params["config"]
        self.dataDir = mirrors.plugin.params["storage-file"]["data-directory"]

        self.mode = self.cfg.get("mode", "full")
        if self.id == "kernel-org":
            validModeList = [
                "web-page-only",
                "full",
            ]
        elif self.id == "kernel-org-pub":
            validModeList = [
                "recent-kernel-only",       # only recent kernel files
                "kernel-only",              # only kernel files
                "full",                     # kernel and auxiliary files
            ]
        else:
            assert False
        if self.mode not in validModeList:
            raise Exception("invalid mode %s" % (self.mode))

    def run(self):
        if self.id == "kernel-org":
            self._runKernelOrgWebSite()
            if self.mode != "web-page-only":
                self._runKernelOrgPubDir()
                pubDir = os.path.join(self.dataDir, "pub")
                os.makedirs(pubDir, exist_ok=True)
                self._runKernelOrgPubDir(pubDir)
            return

        if self.id == "kernel-org-pub":
            self._runKernelOrgPubDir(self.dataDir)
            return

        assert False

    def _runKernelOrgWebSite(self):
        selfDir = os.path.dirname(os.path.realpath(__file__))
        srcWebDir = os.path.join(selfDir, "www")

        # get data from source web site
        latestReleaseVer, mainVerDict = self.__getArguments()

        if self.mode == "web-page-only":
            Util.removeDirContentExclude(self.dataDir)
        else:
            Util.removeDirContentExclude(self.dataDir, "pub")

        shutil.copytree(os.path.join(srcWebDir, "css"), os.path.join(self.dataDir, "css"))
        shutil.copytree(os.path.join(srcWebDir, "images"), os.path.join(self.dataDir, "images"))

        # generate web pages without any hyperlink
        tm = jinja2.Environment(loader=jinja2.FileSystemLoader(srcWebDir)).get_template("index.html.jinja2")
        with open(os.path.join(self.dataDir, "index.html"), "w") as f:
            f.write(tm.render({
                "latest_release_version": latestReleaseVer,
                "mainline_version": mainVerDict["mainline"][0][0],
                "stable_version": mainVerDict["stable"][0][0],
                "longterm_version": mainVerDict["longterm"][0][0],
            }))

    def _runKernelOrgPubDir(self, targetDir):
        rsyncSource = None
        patternList = None
        if True:
            def filterFunc(x):
                return x["protocol"] == "rsync" and x["role"] != "local-mirror"

            if self.mode == "full":
                rsyncSource = mrget.target_urls("mirror://kernel-org-pub", filter_key=filterFunc)[0]
                patternList = []
            elif self.mode == "kernel-only":
                rsyncSource = mrget.target_urls("mirror://kernel-org-pub/linux/kernel", filter_key=filterFunc)[0]       # to utilize chrooted mirrors, for example https://mirrors.tuna.tsinghua.edu.cn/kernel/
                patternList = [
                    "+ /",
                    "+ /v*",
                    "+ /v*/***",
                    "- /**",
                ]
            elif self.mode == "recent-kernel-only":
                rsyncSource = mrget.target_urls("mirror://kernel-org-pub/linux/kernel", filter_key=filterFunc)[0]       # same above
                mainVerDict = self.__getArguments()[1]
                patternList = [
                    "+ /",
                ]
                for verPrefix in set([x[1] for x in itertools.chain.from_iterable(mainVerDict.values())]):
                    patternList += [
                        "+ /v%s.*" % (verPrefix),
                        "+ /v%s.*/***" % (verPrefix),
                    ]
                patternList += [
                    "- /**",
                ]
            else:
                assert False

        cmd = None
        if True:
            cmd = "/usr/bin/rsync -v -a -z --delete --delete-excluded --partial "
            for p in patternList:
                cmd += "-f '%s' " % (p)
            cmd += "%s %s" % (rsyncSource, targetDir)

        subprocess.run(cmd, shell=True, check=True)

    def __getArguments(self):
        latestReleaseVer = None
        mainVerDict = {}

        resp = urllib.request.urlopen(self.url, timeout=60)
        root = lxml.html.parse(resp)

        for aTag in root.xpath(".//td[@id='latest_link']/a"):
            latestReleaseVer = aTag.text
            break

        for tr in root.xpath(".//table[@id='releases']/tr"):
            name = tr.xpath("./td")[0].text
            if name not in ["mainline:", "stable:", "longterm:"]:
                continue
            name = name[:-1]

            ver = tr.xpath("./td")[1].xpath("./strong")[0].text
            m = re.fullmatch("([0-9]+)\\.[0-9].*", ver)
            if m is None:
                continue
            verPrefix = m.group(1)

            if name in mainVerDict:
                mainVerDict[name].append((ver, verPrefix))
            else:
                mainVerDict[name] = [(ver, verPrefix)]

        return (latestReleaseVer, mainVerDict)


class Util:

    @staticmethod
    def forceDelete(path):
        if os.path.islink(path):
            os.remove(path)
        elif os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
        else:
            pass                        # path does not exist, do nothing

    @staticmethod
    def removeDirContentExclude(dirPath, excludeList):
        for fn in os.listdir(dirPath):
            if fn not in excludeList:
                Util.forceDelete(os.path.join(dirPath, fn))


###############################################################################

if __name__ == "__main__":
    with mirrors.plugin.ApiClient() as sock:
        try:
            Main().run()
            sock.progress_changed(100)
        except Exception:
            sock.error_occured(sys.exc_info())
            raise
